package com.iakuil.app.service.base;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.iakuil.app.common.core.BaseService;
import com.iakuil.app.common.core.annotation.Unique;
import com.iakuil.app.common.core.constant.SysConstant;
import com.iakuil.app.common.core.domain.BaseEntity;
import com.iakuil.app.common.core.exception.BusinessException;
import com.iakuil.app.common.core.util.CollectionUtils;
import com.iakuil.app.common.core.util.ObjectUtils;
import com.iakuil.app.common.core.util.ReflectUtils;
import com.iakuil.app.common.core.util.StringUtils;
import com.iakuil.app.repository.base.CrudMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 通用Domain Service实现类（基于MyBatis Plus）
 *
 * @author Kai
 * @date 2021/6/18 16:44
 **/
public abstract class BaseServiceImpl<M extends CrudMapper<T>, T extends BaseEntity<?>> implements BaseService<T> {

    protected Class<T> entityClass;

    @Autowired
    protected M baseMapper;

    public BaseServiceImpl() {
        this.entityClass = getEntityType();
    }

    @Override
    @Transactional(readOnly = true)
    public List<T> list(String... fields) {
        return baseMapper.selectList(new QueryWrapper<T>().select(getColumns(fields)));
    }

    @Override
    @Transactional(readOnly = true)
    public List<T> list(@NonNull T entity, String... fields) {
        return baseMapper.selectList(new QueryWrapper<>(entity).select(getColumns(fields)));
    }

    @Override
    @Transactional(readOnly = true)
    public T getById(Long id) {
        Assert.notNull(id, "ID should not be empty!");
        return baseMapper.selectById(id);
    }

    @Override
    @Transactional(readOnly = true)
    public List<T> listByIds(Long... ids) {
        Assert.notEmpty(ids, "IDs should not be empty!");
        return baseMapper.selectBatchIds(Arrays.stream(ids).filter(Objects::nonNull).collect(Collectors.toSet()));
    }

    @Override
    @Transactional(readOnly = true)
    public List<T> listByIds(Collection<Long> ids) {
        Assert.notEmpty(ids, "IDs should not be empty");
        return baseMapper.selectBatchIds(CollectionUtils.newHashSet(ids));
    }

    @Override
    @Transactional(readOnly = true)
    public T getOne(T entity) {
        return getOne(entity, true);
    }

    @Override
    @Transactional(readOnly = true)
    public T getOne(T entity, boolean throwEx) {
        Assert.notNull(entity, "Entity should not be empty!");
        return throwEx ? baseMapper.selectOne(new QueryWrapper<>(entity), false) : baseMapper.selectOne(new QueryWrapper<>(entity));
    }

    @Override
    @Transactional(readOnly = true)
    public long count(T entity) {
        Assert.notNull(entity, "Entity should not be empty!");
        return baseMapper.selectCount(new QueryWrapper<>(entity));
    }

    @Override
    @Transactional(readOnly = true)
    public boolean exists(T entity) {
        return count(entity) > 0;
    }

    @Override
    @Transactional(readOnly = true)
    public boolean exists(Long id) {
        return getById(id) != null;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean save(T entity) {
        Assert.notNull(entity, "Entity should not be empty!");
        return baseMapper.insert(entity) > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveInBatch(Collection<T> entities) {
        return this.saveInBatch(entities, SysConstant.DEFAULT_BATCH_CHUNK_SIZE);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveInBatch(Collection<T> entities, int chunkSize) {
        Assert.notEmpty(entities, "Entity list should not be empty!");
        List<Collection<T>> chunks = CollectionUtils.partition(CollectionUtils.removeNull(entities), chunkSize);
        int count = 0;
        for (Collection<T> chunk : chunks) {
            count += baseMapper.insertBatchSomeColumn(chunk);
        }
        return count == entities.size();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean modify(T entity) {
        Assert.notNull(entity, "Entity should not be empty!");
        handleVersion(entity);
        return baseMapper.updateById(entity) > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean modifyInBatch(Collection<T> entities) {
        Assert.notEmpty(entities, "Entity list should not be empty!");
        int count = 0;
        for (T entity : CollectionUtils.removeNull(entities)) {
            handleVersion(entity);
            count += baseMapper.updateById(entity);
        }
        return count == entities.size();
    }

    private void handleVersion(T entity) {
        TableInfo tableInfo = getTableInfo();
        if (tableInfo != null && tableInfo.isWithVersion()) {
            Long id = entity.getId();
            Assert.notNull(id, "ID should not be empty!");
            T before = baseMapper.selectById(id);
            Assert.notNull(before, "Invalid ID " + id + "!");
            // 首先获取乐观锁字段名
            String versionField = tableInfo.getVersionFieldInfo().getField().getName();
            // 获取乐观锁字段值，缺省为1
            Object versionValue = ObjectUtils.defaultIfNull(ReflectUtils.getFieldValue(before, versionField), 1L);
            ReflectUtils.setFieldValue(entity, versionField, versionValue);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveOrModify(T entity) {
        Assert.notNull(entity, "Entity should not be empty!");
        Long id = entity.getId();
        boolean result;
        if (id != null && exists(id)) {
            result = modify(entity);
        } else {
            result = save(entity);
        }
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean remove(Long id) {
        Assert.notNull(id, "ID should not be empty!");
        return baseMapper.deleteById(id) > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeByIds(Long... ids) {
        Assert.notEmpty(ids, "IDs should not be empty");
        return removeByIds(Arrays.asList(ids));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeByIds(Collection<Long> ids) {
        Assert.notEmpty(ids, "IDs should not be empty!");
        return baseMapper.deleteBatchIds(ids.stream().filter(Objects::nonNull).collect(Collectors.toSet())) > 0;
    }

    /***
     * 唯一性检测（领域内）
     *
     * <p>新增、修改前调用，自动校验带{@link Unique}注解的字段。
     *
     * @param entity 实体类对象
     */
    public void checkUnique(T entity) {
        Assert.notNull(entity, "Entity should not be empty!");
        TableInfo tableInfo = getTableInfo();
        List<TableFieldInfo> fieldList = tableInfo.getFieldList();
        for (TableFieldInfo tableFieldInfo : fieldList) {
            Field field = tableFieldInfo.getField();
            Unique unique = field.getAnnotation(Unique.class);
            if (ObjectUtils.isEmpty(unique)) {
                continue;
            }

            String name = field.getName();
            Optional.ofNullable(ReflectUtils.getFieldValue(entity, name)).ifPresent(v -> {
                T inst = ReflectUtils.newInstance(entityClass);
                ReflectUtils.setFieldValue(inst, name, v);
                if (baseMapper.exists(Wrappers.query(inst).ne(ObjectUtils.isNotNull(entity.getId()), tableInfo.getKeyColumn(), entity.getId()))) {
                    throw new BusinessException(Objects.toString(unique.value(), "记录值'" + v + "'不唯一"));
                }
            });
        }
    }

    /**
     * 属性名转字段名
     *
     * <p>不匹配的属性名将会被忽略。
     *
     * @param fields 属性名
     * @return 对应字段名
     */
    public String[] getColumns(String[] fields) {
        List<String> allProperties = new ArrayList<>();
        TableInfo tableInfo = getTableInfo();
        allProperties.add(tableInfo.getKeyProperty());
        allProperties.addAll(tableInfo.getFieldList().stream().map(info -> info.getField().getName()).toList());
        return CollectionUtils.intersection(allProperties, Arrays.asList(fields)).stream().map(StringUtils::toUnderlineCase).toArray(String[]::new);
    }

    private TableInfo getTableInfo() {
        return TableInfoHelper.getTableInfo(entityClass);
    }

    @SuppressWarnings("unchecked")
    private Class<T> getEntityType() {
        ParameterizedType parameterizedType = (ParameterizedType) this.getClass().getGenericSuperclass();
        return (Class<T>) parameterizedType.getActualTypeArguments()[1];
    }
}
